Hooks

Catalyst exposes two groups of hooks:

  • routing hooks from catalyst-core in 0.3.x (@tata1mg/router in legacy 0.2.x)
  • native or universal app hooks from catalyst-core/hooks

Hooks Overview

Hook / APIDescriptionWebiOSAndroid
useRouterDataAccess data for all matched routesYesYesYes
useCurrentRouteDataAccess current route fetcher state and dataYesYesYes
getDeviceInfoRead device, screen, and app metadata from the bridgeYesYesYes
useCameraCapture media via the bridge or web fallbackPartialYesYes
useFilePickerSelect files and normalize resultsPartialYesYes
useIntentOpen files or URLs with external appsNoYesYes
useGoogleSignInTrigger Google sign-in through the native shellNoYesYes
useCameraPermissionCheck and request camera permissionPartialYesYes
useNotificationPermissionCheck and request notification permissionNoYesYes
useHapticFeedbackTrigger haptic feedback with platform-aware behaviorNoYesYes
useNotificationSchedule local notifications and manage push setupNoYesYes
useNetworkStatusRead online status and network typeYesYesYes
useDataProtectionUse native data protection and encryption helpersNoYesYes
useSafeAreaRead native safe-area insetsYesYesYes
useAIGenerate text via cloud, native on-device, or in-browser modelsPartialYesYes

Partial means behavior depends on browser support or fallback behavior in the web environment.

Route Hooks

useRouterData

The useRouterData hook provides access to the data for all matched routes.

Import

import { useRouterData } from "catalyst-core";

Return Value

Returns an object containing data for all active routes, keyed by route path.

Usage

import { useRouterData } from "catalyst-core";
const Layout = () => {
const routerData = useRouterData();
// { "/dashboard": { data, error, ... }, "/dashboard/settings": { data, error, ... } }
};

useCurrentRouteData

useCurrentRouteData provides access to the data resolved by serverFetcher or clientFetcher for the current route.

Import

import { useCurrentRouteData } from "catalyst-core";

Return Value

PropertyTypeDescription
dataanyThe data returned by the fetcher function.
errorErrorAn error object if the fetcher function throws an error.
isFetchingbooleanA boolean indicating if a fetch is in progress.
isFetchedbooleanA boolean indicating if a fetch has completed.
refetchfunctionA function to re-run the clientFetcher.
clearfunctionA function to clear the cached data for the current route.

Requirements

This hook only works inside the RouterDataProvider tree. If the app is not wrapped in RouterDataProvider, the hook returns undefined.

Usage

import { useCurrentRouteData } from "catalyst-core";
const ProductPage = () => {
const { data, error, isFetching, isFetched, refetch, clear } = useCurrentRouteData();
return (
<div>
{isFetching && <Spinner />}
{error && <Error message={error.message} />}
{data && <ProductDetails product={data} />}
</div>
);
};

refetch

The refetch function can be used to re-run the clientFetcher for the current route.

const ProductList = () => {
const { data, refetch } = useCurrentRouteData();
const [page, setPage] = useState(1);
useEffect(() => {
refetch({ page });
}, [page]);
return <div>...</div>;
};
ProductList.clientFetcher = async ({ params }, { store }, { page = 1 }) => {
const response = await fetch(`/api/products?page=${page}`);
return response.json();
};

clear

The clear function can be used to remove the cached data for the current route.

const { clear } = useCurrentRouteData();
useEffect(() => {
return () => clear();
}, []);

Universal App Hooks

Import universal hooks from catalyst-core/hooks:

import {
useCamera,
useFilePicker,
useIntent,
useGoogleSignIn,
useCameraPermission,
useNotificationPermission,
useHapticFeedback,
useNotification,
useNetworkStatus,
useDataProtection,
useSafeArea,
} from "catalyst-core/hooks";

Most native hooks follow a common pattern:

  • data
  • loading
  • error
  • progress
  • isWeb
  • isNative
  • execute
  • clear
  • clearError

getDeviceInfo

Read device, screen, and app metadata from the native bridge. getDeviceInfo is exposed by WebBridge.init() and on window.WebBridge; it is not a React hook exported from catalyst-core/hooks.

Import

import WebBridge from "catalyst-core/WebBridge";

Returns

Resolves to an object with normalized device metadata.

PropertyTypeDescription
modelstringDevice model, or the browser user agent on web
manufacturerstringDevice manufacturer, or browser on web
platformstringios, android, or web
screenWidthnumberScreen width in pixels
screenHeightnumberScreen height in pixels
screenDensitynumberScreen scale or pixel density
appInfoobject | string | nullApp metadata provided by the native shell, when available
securityobjectAndroid security check state, when available

Usage

const { getDeviceInfo } = WebBridge.init();
async function logDeviceInfo() {
const deviceInfo = await getDeviceInfo();
console.log(deviceInfo.platform, deviceInfo.model);
}

If the bridge is already initialized, you can also call window.WebBridge.getDeviceInfo(). On web, getDeviceInfo() resolves with browser and screen information instead of throwing.

useCamera

Access camera capture through the native bridge. The hook exposes a standardized stateful interface and camera-specific aliases.

Returns

PropertyTypeDescription
dataobject | nullCaptured file payload including file data and transport metadata
loadingbooleanCapture or permission flow in progress
errorobject | nullStandardized error object
progressobject | nullProgress information during the capture flow
isWebbooleanRunning in a browser context
isNativebooleanRunning inside the native shell
executefunctionPrimary camera action entrypoint
permissionobjectCamera permission state
takePhotofunctionSemantic alias for camera capture
clearfunctionClear captured data and reset state
clearErrorfunctionClear error state only

Usage

function PhotoCapture() {
const { takePhoto, loading, error, data, isNative } = useCamera();
const handleCapture = async () => {
const photo = await takePhoto();
if (photo) {
console.log("Photo captured:", photo.fileName);
}
};
return (
<button onClick={handleCapture} disabled={loading || !isNative}>
Take Photo
</button>
);
}

useFilePicker

Open the native file picker and receive a normalized result payload.

Returns

PropertyTypeDescription
dataobject | nullNormalized file picker payload
selectedFilesarraySelected file entries
loadingbooleanPicker flow in progress
errorobject | nullStandardized error object
executefunctionOpen the file picker
pickFilefunctionAlias for execute
getFileObjectfunctionConvert one selected result into a browser File
getAllFileObjectsfunctionConvert all selected results into File[]
clearfunctionClear picker state
clearErrorfunctionClear error state only

Usage

function FileUpload() {
const { pickFile, getAllFileObjects, loading } = useFilePicker();
const handleSelectFile = async () => {
pickFile({ mimeType: "application/pdf", multiple: true, maxFiles: 3 });
};
return <button onClick={handleSelectFile} disabled={loading}>Select File</button>;
}

useHapticFeedback

Trigger platform-specific haptic feedback with a standardized interface and semantic shortcuts.

Returns

PropertyTypeDescription
execute(feedbackType?, options?) => Promise<boolean>Trigger haptic feedback
isSupportedbooleanHaptics available on device
lightfunctionLight feedback shortcut
mediumfunctionMedium feedback shortcut
heavyfunctionHeavy feedback shortcut
successfunctionSuccess feedback shortcut
warningfunctionWarning feedback shortcut
errorHapticfunctionError feedback shortcut

Usage

function FeedbackButton() {
const { medium } = useHapticFeedback();
return <button onClick={() => medium()}>Submit</button>;
}

useIntent

Open a file or URL with an external native app.

Returns

PropertyTypeDescription
executefunctionOpen the target URL with the provided MIME type
loadingbooleanIntent flow in progress
errorobject | nullStandardized error object
isNativebooleanRunning inside the native shell
clearfunctionClear result state
clearErrorfunctionClear error state only

Usage

function OpenInvoiceButton({ url }) {
const { execute, loading } = useIntent();
return (
<button onClick={() => execute(url, "application/pdf")} disabled={loading}>
Open Invoice
</button>
);
}

useCameraPermission

Check or request camera permission through the native bridge.

function CameraPermissionButton() {
const { permission, isLoading } = useCameraPermission();
return <button disabled={isLoading}>Camera permission: {permission || "checking"}</button>;
}

useNotificationPermission

Check or request notification permission before registering for push notifications.

function NotificationPermissionButton() {
const { permission, isLoading } = useNotificationPermission();
return <button disabled={isLoading}>Notification permission: {permission || "checking"}</button>;
}

useNotification

Manage local notifications and push registration from one hook.

Returns

MethodTypeDescription
dataobject | nullLatest notification result data
loadingbooleanNotification operation in progress
errorobject | nullStandardized error object
permissionStatusstringNotification permission status
pushTokenstring | nullCurrent push token when available
scheduleLocalfunctionSchedule a local notification
cancelLocalfunctionCancel a scheduled local notification
registerForPushfunctionRegister for push notifications
subscribeToTopicfunctionSubscribe to a notification topic
unsubscribeFromTopicfunctionUnsubscribe from a notification topic
getSubscribedTopicsfunctionRead current topic subscriptions

Usage

function NotificationExample() {
const { scheduleLocal, registerForPush, subscribeToTopic } = useNotification();
return (
<>
<button onClick={() => scheduleLocal({ title: "Hey!", body: "You got a message" })}>
Send Local Notification
</button>
<button onClick={() => { registerForPush(); subscribeToTopic("news"); }}>
Enable Push Notifications
</button>
</>
);
}

Requirements

Push-related notification features require WEBVIEW_CONFIG.notifications.enabled = true and the relevant Firebase platform files in the native projects.

useGoogleSignIn

Trigger Google sign-in through the native shell.

function GoogleLoginButton() {
const { signIn, loading, error } = useGoogleSignIn();
return <button onClick={signIn} disabled={loading}>Continue with Google</button>;
}

useNetworkStatus

Read connectivity state from the native bridge, with a browser fallback.

PropertyTypeDescription
onlinebooleanCurrent connectivity state
typestring | nullNetwork type such as wifi or cellular
errorstring | nullConnectivity error, if any
function ConnectivityBanner() {
const { online, type } = useNetworkStatus();
if (online) return null;
return <div>Offline{type ? ` (${type})` : ""}</div>;
}

useDataProtection

Use native data protection and encryption helpers exposed through the bridge.

function ProtectedAction() {
const { setScreenSecure, loading } = useDataProtection();
return <button onClick={() => setScreenSecure(true)} disabled={loading}>Protect Screen</button>;
}

useSafeArea

Read safe-area insets in pixels. On web and SSR, all values are 0.

PropertyTypeDescription
topnumberTop inset
rightnumberRight inset
bottomnumberBottom inset
leftnumberLeft inset
function ScreenShell({ children }) {
const safeArea = useSafeArea();
return <main style={{ paddingTop: safeArea.top }}>{children}</main>;
}

useAI

Generate text through one of three providers, chosen with the provider option — the hook picks the underlying implementation for you:

providerImplementationWhere it runs
"openai" | "gemini" (default)useCloudAINode server route (POST /ai/:provider/stream or /generate)
"native"useNativeAI, falls back to useCloudAI if window.NativeBridge is unavailableOn-device LiteRT-LM engine, via an embedded Ktor server (POST /framework-{sessionId}/ai/stream or /generate)
"transformers"useWebAIexperimental: in-browser inference quality and WebGPU/WASM backend selection aren't reliable yet on larger modelsIn-browser, via a Web Worker running Transformers.js

Requires @catalyst/ai to be installed in the app.

Import

import { useAI } from "catalyst-core/hooks";

Options

OptionTypeDescription
providerstring"openai", "gemini", "native", or "transformers". Defaults to the cloud provider configured in AI_CONFIG.browser.
modelstringModel id. Required for transformers; optional override for cloud/native.
genConfigobjectDefault generation config, merged with any per-call genConfig passed to generate(). See below.
systemPromptstringSystem prompt prepended to every generation.
sessionModestring"stateless" (default) or "stateful" — see Stateful Sessions below.
attachmentComponentsobjectMap of component name → { attrs, hint }, enabling structured output. See Attachment Components below.

Returns

PropertyTypeDescription
outputstringAccumulated generation text so far.
streamingbooleantrue while SSE tokens are actively arriving.
loadingbooleantrue while a request is in flight (covers both the stream and non-stream paths).
errorError | nullError from the last generate() call, if any.
generatefunctiongenerate({ messages, genConfig }) — triggers a request. messages is [{ role, content }, ...].
cancelfunctionAbort the in-flight generation.
resetfunctionClear output, error, and conversation state; ends the current session.
clearErrorfunctionClear error only.
conversationIdstring | nullPresent when sessionMode="stateful" — see below for what it means per provider.
isLocal / isNative / isWebbooleanIndicates which provider actually served the request.
modelReadybooleanFor native/transformers: false while the model is downloading or the engine is initializing.
downloadProgressobject | nullFor transformers: { file, percent, status }.
nativeDownloadProgressobject | nullFor native: { phase, percent, detail } (phase is "engine_init" or "model_fetch").
nativeLogsstring[]For native: recent native-side log lines.
metricsobject | nullPer-generation metrics from the most recent call — shape differs per provider, see below.
getSessionMetricsfunctionReturns an aggregate across every generation this session, or null if none yet.
resetSessionMetricsfunctionClears the accumulated metrics history, without touching output or conversation state.

Usage

function Summarizer() {
const { generate, output, streaming, loading, error } = useAI({
provider: "openai",
genConfig: { temperature: 0.3, maxTokens: 512 },
});
const handleSummarize = (document) => {
generate({ messages: [{ role: "user", content: `Summarize: ${document}` }] });
};
return (
<div>
<button onClick={() => handleSummarize(doc)} disabled={loading}>Summarize</button>
{streaming && <Spinner />}
{error && <p>{error.message}</p>}
{output && <p>{output}</p>}
</div>
);
}

genConfig

{
temperature: number, // 0-1
maxTokens: number | null,
topP: number,
repetitionPenalty: number, // native/web only
noRepeatNgramSize: number, // native/web only
stream: boolean,
}

stream: true uses the SSE path; stream: false uses a single-JSON-response path. Cloud and native both expose real stream/generate HTTP routes for this. The transformers provider ignores genConfig.stream — the Worker always runs the full generation and streams tokens back internally via postMessage.

Stateful Sessions

Pass sessionMode: "stateful" to carry conversation context across calls. The mechanism is different for each provider, since each has a different notion of "session":

  • Cloud — the client sends conversationId in the request body; the server translates it into the underlying provider's own continuation token (previous_response_id for OpenAI, previous_interaction_id for Gemini) and returns a fresh conversationId with each response.
  • Native — the client sends conversationId to the Ktor route; the Android engine reuses the same LiteRT-LM Conversation object when the incoming id matches its current one, otherwise starts a new one. reset() also tells the native bridge to drop that object.
  • Web (experimental) — there is no engine-side session at all. "Stateful" here means the hook accumulates prior { role, content } turns client-side and replays the full history into every generate() call — real chat, not KV-cache reuse. conversationId is just a locally-generated id for the UI to key off; it carries no meaning beyond the hook instance.

reset() clears output, conversation history, and conversationId in all three cases. sessionMode: "stateless" (the default) makes every generate() call independent.

Metrics

metrics (after a call) and getSessionMetrics() (aggregated across the session) differ by provider:

  • Cloud includes cost, cachedTokens, cacheSavings, and a byProvider breakdown, alongside ttftMs/tps/totalTokens.
  • Native and web have no billing, so their metrics are the leaner { device, ttftMs, tps, totalTokens, genMs } (web additionally reports dtype, loadMs, and downloadBytes for the model download).

Call resetSessionMetrics() to clear accumulated history without ending the current conversation.

Attachment Components

Pass attachmentComponents to have the model return structured, renderable output instead of plain text:

useAI({
attachmentComponents: {
InfoCard: { attrs: { title: "string", type: "info|warning|success" }, hint: "1 sentence callout" },
},
});

The model is instructed (via an injected system prompt) to wrap relevant output in {% ComponentName attr='val' %}body{% /ComponentName %} tags. output contains the raw tagged text — render it with renderOutput(output, streaming) from utils/ai.js, or the AttachmentRenderer component. Each key must correspond to a registered React component.

Requirements

Requires @catalyst/ai to be installed (npm install @catalyst/ai). Without it, useAI logs an error and returns an inert hook (all booleans false, generate/cancel/reset are no-ops).